<?php

// ------------------------------------------------------------------------
// |@Author       : Jarmin <edshop@qq.com>
// |@----------------------------------------------------------------------
// |@Date         : 2025-04-19 19:32:39
// |@----------------------------------------------------------------------
// |@LastEditTime : 2025-04-27 22:21:09
// |@----------------------------------------------------------------------
// |@LastEditors  : Jarmin <jarmin@ladmin.cn>
// |@----------------------------------------------------------------------
// |@Description  : Token服务类
// |@----------------------------------------------------------------------
// |@FilePath     : Token.php
// |@----------------------------------------------------------------------
// |@Copyright (c) 2025 http://www.ladmin.cn   All rights reserved.
// ------------------------------------------------------------------------

namespace think\admin\service;

use think\admin\Service;

/**
 * Token服务类
 * Class Token
 * @package app\admin\service
 */
class Token extends Service
{
    /**
     * 创建访问令牌
     *
     * @param array $userData 用户数据
     * @param int $expires 过期时间（秒）
     * @return array 令牌信息
     */
    public function createAccessToken(array $userData, int $expires = 7200): array
    {
        // 移除敏感信息
        unset($userData['password'], $userData['pay_password'], $userData['salt']);

        // 基本载荷信息
        $now = time();
        $jti = md5(uniqid('JWT', true)); // 唯一标识

        $payload = [
            'iat' => $now,                  // 签发时间
            'nbf' => $now,                  // 生效时间
            'exp' => $now + $expires,       // 过期时间
            'jti' => $jti,                  // 唯一标识
            'iss' => request()->domain(),   // 签发者
            'user' => [                     // 用户信息
                'id' => $userData['id'],
                'username' => $userData['username'],
                'is_super' => $userData['is_super'],
                'status' => $userData['status'] ?? 1
            ],
            'device' => md5(request()->header('User-Agent')), // 设备指纹
        ];

        // 生成令牌
        $token = $this->encodeToken($payload);

        // 存储令牌记录
        $this->saveTokenRecord($jti, $userData['id'], $expires);

        return [
            'access_token' => $token,
            'token_type' => 'Bearer',
            'expires_in' => $expires,
            'user' => $payload['user']
        ];
    }

    /**
     * 创建刷新令牌
     *
     * @param int $userId 用户ID
     * @param int $expires 过期时间（秒）
     * @return string 刷新令牌
     */
    public function createRefreshToken(int $userId, int $expires = 604800): string
    {
        $now = time();
        $jti = md5(uniqid('REFRESH', true));

        $payload = [
            'iat' => $now,
            'nbf' => $now,
            'exp' => $now + $expires,
            'jti' => $jti,
            'sub' => 'refresh',
            'uid' => $userId
        ];

        $token = $this->encodeToken($payload);

        // 存储刷新令牌记录
        $this->saveTokenRecord($jti, $userId, $expires, 'refresh');

        return $token;
    }

    /**
     * 验证令牌
     *
     * @param string $token 令牌
     * @param bool $checkRevoked 是否检查吊销状态
     * @return array|false 令牌载荷或失败
     */
    public function validateToken(string $token, bool $checkRevoked = true): array|false
    {
        try {
            $payload = $this->decodeToken($token);

            // 验证令牌是否已被吊销
            if ($checkRevoked && isset($payload['jti'])) {
                $isValid = $this->repositoryToken->isTokenValid($payload['jti']);
                if (!$isValid) {
                    return false;
                }
            }

            // 验证设备信息，防止token被盗用（可选）
            if (isset($payload['device']) &&
                $payload['device'] !== md5(request()->header('User-Agent'))) {
                return false;
            }

            return $payload;
        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * 刷新访问令牌
     *
     * @param string $refreshToken 刷新令牌
     * @return array|false 新的访问令牌信息或失败
     */
    public function refreshAccessToken(string $refreshToken): array|false
    {
        // 验证刷新令牌
        $payload = $this->validateToken($refreshToken);

        if (!$payload || $payload['sub'] !== 'refresh') {
            return false;
        }

        // 获取用户信息
        $userId = $payload['uid'];
        $user = $this->repositoryUser->getUserById($userId);

        if (!$user) {
            return false;
        }

        // 吊销原刷新令牌
        $this->repositoryToken->revokeToken($payload['jti'], $userId);

        // 创建新的访问令牌和刷新令牌
        $accessToken = $this->createAccessToken($user);
        $newRefreshToken = $this->createRefreshToken($userId);

        $accessToken['refresh_token'] = $newRefreshToken;

        return $accessToken;
    }

    /**
     * 吊销令牌
     *
     * @param string $token 令牌
     * @return bool 是否成功
     */
    public function revokeToken(string $token): bool
    {
        try {
            $payload = $this->decodeToken($token);

            if (isset($payload['jti']) && isset($payload['user']['id'])) {
                return $this->repositoryToken->revokeToken(
                    $payload['jti'],
                    $payload['user']['id']
                );
            }

            return false;
        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * 吊销用户所有令牌
     *
     * @param int $userId 用户ID
     * @return bool 是否成功
     */
    public function revokeAllUserTokens(int $userId): bool
    {
        return $this->repositoryToken->revokeAllUserTokens($userId);
    }

    /**
     * 编码令牌
     *
     * @param array $payload 载荷
     * @return string 令牌
     */
    protected function encodeToken(array $payload): string
    {
        $secret = config('jwt.secret');
        $algo = config('jwt.algo', 'HS256');

        return $this->serviceJWT->encode($payload, $secret, $algo);
    }

    /**
     * 解码令牌
     *
     * @param string $token 令牌
     * @return array 载荷
     */
    protected function decodeToken(string $token): array
    {
        $secret = config('jwt.secret');

        $decoded = $this->serviceJWT->decode($token, $secret, ['HS256', 'HS384', 'HS512']);
        return (array)$decoded;
    }

    /**
     * 保存令牌记录
     *
     * @param string $jti 令牌ID
     * @param int $userId 用户ID
     * @param int $expires 过期时间（秒）
     * @param string $type 令牌类型
     * @return bool 是否成功
     */
    protected function saveTokenRecord(string $jti, int $userId, int $expires, string $type = 'access'): bool
    {
        return $this->repositoryToken->saveToken([
            'jti' => $jti,
            'user_id' => $userId,
            'type' => $type,
            'is_revoked' => 0,
            'device' => request()->header('User-Agent'),
            'ip' => request()->ip(),
            'created_at' => date('Y-m-d H:i:s'),
            'expired_at' => date('Y-m-d H:i:s', time() + $expires),
        ]);
    }

    /**
     * 定期清理过期令牌
     *
     * @return int 清理数量
     */
    public function cleanExpiredTokens(): int
    {
        return $this->repositoryToken->cleanExpiredTokens();
    }

    /**
     * 创建临时令牌
     *
     * @param array $data 临时数据
     * @param int $expireMinutes 有效期（分钟）
     * @return string 临时令牌
     */
    public function createTempToken(array $data, int $expireMinutes = 10): string
    {
        $now = time();
        $expires = $now + ($expireMinutes * 60);
        $token = md5(uniqid('TEMP', true));

        // 存储临时令牌数据
        cache("temp_token:{$token}", [
            'data' => $data,
            'created_at' => $now,
            'expires_at' => $expires
        ], $expireMinutes * 60);

        return $token;
    }

    /**
     * 验证临时令牌
     *
     * @param string $token 临时令牌
     * @return array|false 临时数据或失败
     */
    public function verifyTempToken(string $token): array|false
    {
        if (empty($token)) {
            return false;
        }

        $cacheKey = "temp_token:{$token}";
        $tokenData = cache($cacheKey);

        if (empty($tokenData)) {
            return false;
        }

        // 检查是否过期
        if (time() > $tokenData['expires_at']) {
            cache($cacheKey, null); // 删除过期令牌
            return false;
        }

        return $tokenData['data'];
    }

    /**
     * 删除临时令牌
     *
     * @param string $token 临时令牌
     * @return bool 是否成功
     */
    public function removeTempToken(string $token): bool
    {
        if (empty($token)) {
            return false;
        }

        $cacheKey = "temp_token:{$token}";
        cache($cacheKey, null);
        return true;
    }
}
